//---------------------------------------------------------------------
//  This file is part of the Background Motion solution.
// 
//  Copyright (C) Mindscape (TM).  All rights reserved.
//  http://www.mindscape.co.nz
// 
//  THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using Mindscape.Web.UI.WebControls.Exceptions;

namespace Mindscape.Web.UI.WebControls.Binding
{
  public enum BindingModes
  {
    /// <summary>
    /// Databinding occurs for DataBind() and Unbind()
    /// </summary>
    TwoWay,
    /// <summary>
    /// DataBinding occurs for DataBind() only
    /// </summary>
    OneWay,
    /// <summary>
    /// No binding occurs
    /// </summary>
    None
  }

  [ToolboxData("<{0}:WebDataBindingItem runat=server />")]
  [Category("Data")]
  [DefaultEvent("Validate")]
  [Description(
    "An individual databinding item that allows you to bind a source binding source - a database field or Object property typically - to a target control property"
    )]
  [Serializable]
  public class WebDataBindingItem : Control
  {
    private BindingModes _bindingMode = BindingModes.TwoWay;
    private WebDataBinder _binder = null;
    private Control _targetControlInstance = null;
    private object _bindingSourceObject = null;
    private string _targetControlId = "";
    private string _targetControlProperty = "Text";
    private string _bindingSource = "";
    private string _bindingSourceMember = "";
    private string _displayFormat = "";
    private string _dataSourceTextField = "";
    private string _dataSourceValueField = "";

    protected new bool DesignMode = (HttpContext.Current == null);

    public WebDataBindingItem()
    {
    }

    public WebDataBindingItem(WebDataBinder Parent)
    {
      _binder = Parent;
    }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public WebDataBinder Binder
    {
      get { return _binder; }
      set { _binder = value; }
    }

    #region Target Control Properties

    [NotifyParentProperty(true)]
    [Description("The ID of the control to that is bound.")]
    [DefaultValue("")]
    [TypeConverter(typeof(ControlIDConverter))]
    [Browsable(true)]
    public string TargetControl
    {
      get { return _targetControlId; }
      set
      {
        _targetControlId = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    [NotifyParentProperty(true)]
    [Description("Property that is bound on the target control")]
    [DefaultValue("Text")]
    public string TargetControlProperty
    {
      get { return _targetControlProperty; }
      set
      {
        _targetControlProperty = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    [NotifyParentProperty(true)]
    [Description("The Property to use for the TextField when using DataSource binding.")]
    [DefaultValue("")]
    public string DataSourceTextField
    {
      get { return _dataSourceTextField; }
      set
      {
        _dataSourceTextField = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    [NotifyParentProperty(true)]
    [Description("The Property to use for the ValueField when using DataSource binding.")]
    [DefaultValue("")]
    public string DataSourceValueField
    {
      get { return _dataSourceValueField; }
      set
      {
        _dataSourceValueField = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    [NotifyParentProperty(false)]
    [Description("An instance value for the controls")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false)]
    public Control TargetControlInstance
    {
      get { return _targetControlInstance; }
      set { _targetControlInstance = value; }
    }

    #endregion

    #region Binding Source Properties

    [NotifyParentProperty(true)]
    [Description("The name of the object to bind to")]
    [DefaultValue("")]
    public string BindingSource
    {
      get { return _bindingSource; }
      set
      {
        _bindingSource = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    [NotifyParentProperty(true)]
    [Description("The name of the property or field to bind to")]
    [DefaultValue("")]
    public string BindingSourceMember
    {
      get { return _bindingSourceMember; }
      set
      {
        _bindingSourceMember = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public object BindingSourceObject
    {
      get { return _bindingSourceObject; }
      set { _bindingSourceObject = value; }
    }

    #endregion

    /// <summary>
    /// Format Expression ( {0:c) ) applied to the binding source when it's displayed.
    /// Watch out for two way conversion issues when formatting this way. If you create
    /// expressions and you are also saving make sure the format used can be saved back.
    /// </summary>
    [NotifyParentProperty(true)]
    [Description("Format Expression ( {0:c) ) applied to the binding source when it's displayed.")]
    [DefaultValue("")]
    public string DisplayFormat
    {
      get { return _displayFormat; }
      set
      {
        _displayFormat = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    /// <summary>
    /// If set requires that the control contains a value, otherwise a validation error is thrown
    /// Useful mostly on TextBox controls only.
    /// </summary>
    [NotifyParentProperty(true)]
    [Description(
      "If set requires that the control contains a value, otherwise a validation error is thrown - recommended only on TextBox controls."
      )]
    [DefaultValue(false)]
    public bool IsRequired
    {
      get { return _IsRequired; }
      set
      {
        _IsRequired = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    private bool _IsRequired = false;

    /// <summary>
    /// A descriptive name for the field used for error display
    /// </summary>
    [Description("A descriptive name for the field used for error display")]
    [DefaultValue("")]
    [NotifyParentProperty(true)]
    public string FriendlyName
    {
      get { return _friendlyName; }
      set
      {
        _friendlyName = value;
        if (DesignMode && Binder != null)
        {
          Binder.NotifyDesigner();
        }
      }
    }

    private string _friendlyName = "";

    /// <summary>
    /// Determines how databinding and unbinding is done on the target control. 
    /// One way only fires DataBind() and ignores Unbind() calls. 
    /// Two-way does both. None effectively turns off binding.
    /// </summary>
    [Description(
      "Determines how databinding and unbinding is done on the target control. One way only fires DataBind() and ignores Unbind() calls. Two-way does both"
      )]
    [Browsable(true)]
    [DefaultValue(BindingModes.TwoWay)]
    public BindingModes BindingMode
    {
      get { return _bindingMode; }
      set { _bindingMode = value; }
    }

    /// <summary>
    /// Use this event to hook up special validation logic. Called after binding completes. Return false to indicate validation failed
    /// </summary>
    [Browsable(true)]
    [Description(
      "Use this event to hook up special validation logic. Called after binding completes. Return false to indicate validation failed"
      )]
    public event DataBindingItemValidate Validate;

    /// <summary>
    /// Fires the Validation Event
    /// </summary>
    /// <returns></returns>
    public bool OnValidate()
    {
      if (Validate != null)
      {
        DataBindingValidationEventArgs Args = new DataBindingValidationEventArgs();
        Args.DataBindingItem = this;

        Validate(this, Args);

        if (!Args.IsValid)
        {
          return false;
        }
      }

      return true;
    }

    /// <summary>
    /// Binds a source object and property to a control's property. For example
    /// you can bind a business object to a the text property of a text box, or 
    /// a DataRow field to a text box field. You specify a binding source object 
    /// (Customer.Entity or Customer.DataRow) and property or field(Company, FirstName)
    /// and bind it to the control and the property specified (Text).
    /// </summary>
    public new void DataBind()
    {
      if (BindingMode == BindingModes.None)
      {
        return;
      }

      if (Binder != null)
      {
        DataBind(Binder.Page);
      }

      DataBind(Page);
    }

    /// <summary>
    /// Binds a source object and property to a control's property. For example
    /// you can bind a business object to a the text property of a text box, or 
    /// a DataRow field to a text box field. You specify a binding source object 
    /// (Customer.Entity or Customer.DataRow) and property or field(Company, FirstName)
    /// and bind it to the control and the property specified (Text).
    /// </summary>
    /// <param name="WebPage">the Base control that binding source objects are attached to</param>
    public void DataBind(Control WebPage)
    {
      Control ActiveControl = null;
      string bindingSource = BindingSource;
      object bindingSourceObject = BindingSourceObject;
      string bindingSourceMember = BindingSourceMember;
      string targetControlProperty = TargetControlProperty;

      // short circuit if we have switching this to no-bind
      if (BindingMode == BindingModes.None)
      {
        return;
      }

      if (bindingSourceObject == null &&
        string.IsNullOrEmpty(bindingSource) ||
          string.IsNullOrEmpty(bindingSourceMember))
      {
        return;
      }

      if (TargetControlInstance != null)
      {
        ActiveControl = TargetControlInstance;
      }
      else
      {
        ActiveControl = WebDataBindingHelpers.FindControlRecursive(WebPage, TargetControl);
      }

      try
      {
        if (ActiveControl == null)
        {
          throw new ApplicationException();
        }

        TargetControlInstance = ActiveControl;

        if (bindingSourceObject == null)
        {
          if (bindingSource == "this" || bindingSource.ToLower() == "me")
          {
            bindingSourceObject = WebPage;
          }
          else
          {
            bindingSourceObject = WebDataBindingHelpers.GetPropertyEx(WebPage, bindingSource);
          }
        }

        // check binding source assignment
        if (bindingSourceObject == null)
        {
          throw new WebBindingException("Invalid BindingSource");
        }

        // check the target property we are binding to, and special case of "DataSource" direct bind
        if (targetControlProperty == "DataSource" && (ActiveControl is DataBoundControl || ActiveControl is Repeater))
        {
          object sourceValue = WebDataBindingHelpers.GetPropertyEx(bindingSourceObject, bindingSourceMember);

          if (ActiveControl is DataBoundControl)
          {
            ((DataBoundControl)ActiveControl).DataSource = sourceValue;

            // custom property assignment
            if (ActiveControl is ListControl)
            {
              ((ListControl)ActiveControl).DataTextField = DataSourceTextField;
              ((ListControl)ActiveControl).DataValueField = DataSourceValueField;
            }

            ((DataBoundControl)ActiveControl).DataBind();
          }
          else if (ActiveControl is Repeater)
          {
            ((Repeater)ActiveControl).DataSource = sourceValue;
            ((Repeater)ActiveControl).DataBind();
          }
        }
        else
        {
          // otherwise we are doing a property assignment bind
          object loValue = WebDataBindingHelpers.GetPropertyEx(bindingSourceObject, bindingSourceMember);
          object loBindValue = WebDataBindingHelpers.GetProperty(ActiveControl, targetControlProperty);
          string lcBindingSourceType = loBindValue.GetType().Name;

          if (loValue == null)
          {
            if (lcBindingSourceType == "String")
            {
              WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty, "");
            }
            else if (lcBindingSourceType == "Boolean")
            {
              WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty, false);
            }
            else
            {
              WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty, "");
            }
          }
          else
          {
            if (String.IsNullOrEmpty(DisplayFormat))
            {
              object property = WebDataBindingHelpers.GetProperty(ActiveControl, targetControlProperty);

              if (property.GetType().Equals(typeof(string)))
              {
                WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty, loValue.ToString());
              }
              else
              {
                WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty, loValue);
              }
            }
            else
            {
              Type propertyType = WebDataBindingHelpers.GetPropertyType(bindingSourceObject, bindingSourceMember);

              if (propertyType == typeof(DateTime))
              {
                WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty, ((DateTime)loValue).ToString(DisplayFormat));
              }
              else
              {
                WebDataBindingHelpers.SetProperty(ActiveControl, targetControlProperty,
                 String.Format(DisplayFormat, loValue));
              }
            }
          }
        }
      }
      catch (Exception ex)
      {
        throw (new WebBindingException("Unable to bind " + bindingSource + "." + bindingSourceMember + " : " +
          ex.Message));
      }
    }

    /// <summary>
    /// Unbinds control properties back into the control source.
    /// 
    /// This method uses reflection to lift the data out of the control, then 
    /// parses the string value back into the type of the data source. If an error 
    /// occurs the exception is not caught internally, but generally the 
    /// FormUnbindData method captures the error and assigns an error message to 
    /// the BindingErrorMessage property of the control.
    /// </summary>
    public void Unbind()
    {
      if (BindingMode != BindingModes.TwoWay)
      {
        return;
      }

      if (Binder != null)
      {
        Unbind(Binder.Page);
      }

      Unbind(Page);
    }

    public void Unbind(Control WebPage)
    {
      Control ActiveControl = null;
      Type bindingSourceType = null;
      string bindingSource = BindingSource;
      object bindingSourceObject = BindingSourceObject;
      string bindingSourceMember = BindingSourceMember;
      string bindingProperty = TargetControlProperty;
      string BindingSourceType;

      // only unbind in two way databinding mode
      if (BindingMode != BindingModes.TwoWay)
      {
        return;
      }

      if (TargetControlInstance != null)
      {
        ActiveControl = TargetControlInstance;
      }
      else
      {
        ActiveControl = WebDataBindingHelpers.FindControlRecursive(WebPage, TargetControl);
      }

      if (ActiveControl == null)
      {
        throw new ApplicationException("Invalid Control Id");
      }

      TargetControlInstance = ActiveControl;

      if (bindingSourceObject == null && string.IsNullOrEmpty(bindingSource) ||
        string.IsNullOrEmpty(bindingSourceMember))
      {
        return;
      }

      if (bindingSourceObject == null)
      {
        if (bindingSource == null || bindingSource.Length == 0 || bindingSourceMember == null ||
          bindingSourceMember.Length == 0)
        {
          return;
        }

        if (bindingSource == "this" || bindingSource.ToLower() == "me")
        {
          bindingSourceObject = WebPage;
        }
        else
        {
          bindingSourceObject = WebDataBindingHelpers.GetPropertyEx(WebPage, bindingSource);
        }
      }

      if (bindingSourceObject == null)
      {
        throw new ApplicationException("Invalid BindingSource");
      }

      // Retrieve the new value from the control
      object ControlValue = WebDataBindingHelpers.GetPropertyEx(ActiveControl, bindingProperty);

      // Check for Required values not being blank
      if (IsRequired && (string)ControlValue == "")
      {
        throw new RequiredFieldException();
      }

      MemberInfo[] MInfo =
        bindingSourceObject.GetType().GetMember(bindingSourceMember, WebDataBindingHelpers.MemberAccess);
      if (MInfo[0].MemberType == MemberTypes.Field)
      {
        FieldInfo Field = (FieldInfo)MInfo[0];
        BindingSourceType = Field.FieldType.Name;
        bindingSourceType = Field.FieldType;
      }
      else
      {
        PropertyInfo loField = (PropertyInfo)MInfo[0];
        BindingSourceType = loField.PropertyType.Name;
        bindingSourceType = loField.PropertyType;
      }

      object AssignedValue;

      if (bindingSourceType == typeof(string))
      {
        AssignedValue = ControlValue;
      }
      else if (bindingSourceType == typeof(Int16))
      {
        Int16 TValue = 0;
        if (
          !Int16.TryParse((string)ControlValue, NumberStyles.Integer, Thread.CurrentThread.CurrentCulture.NumberFormat,
            out TValue))
        {
          throw new WebBindingException("Invalid numeric input");
        }
        else
        {
          AssignedValue = TValue;
        }
      }
      else if (bindingSourceType == typeof(Int32))
      {
        Int32 TValue = 0;
        if (
          !Int32.TryParse((string)ControlValue, NumberStyles.Integer, Thread.CurrentThread.CurrentCulture.NumberFormat,
            out TValue))
        {
          throw new WebBindingException("Invalid numeric input");
        }
        else
        {
          AssignedValue = TValue;
        }
      }
      else if (bindingSourceType == typeof(Int64))
      {
        Int64 TValue = 0;
        if (
          !Int64.TryParse((string)ControlValue, NumberStyles.Integer, Thread.CurrentThread.CurrentCulture.NumberFormat,
            out TValue))
        {
          throw new WebBindingException("Invalid numeric input");
        }
        else
        {
          AssignedValue = TValue;
        }
      }
      else if (bindingSourceType == typeof(byte))
      {
        AssignedValue = Convert.ToByte(ControlValue);
      }
      else if (bindingSourceType == typeof(decimal) | bindingSourceType == typeof(decimal?))
      {
        AssignedValue = Decimal.Parse((string)ControlValue, NumberStyles.Any);
      }
      else if (bindingSourceType == typeof(double))
      {
        AssignedValue = Double.Parse((string)ControlValue, NumberStyles.Any);
      }
      else if (bindingSourceType == typeof(bool))
      {
        AssignedValue = ControlValue;
      }
      else if (bindingSourceType == typeof(DateTime))
      {
        DateTime TValue = DateTime.MinValue;

        if (
          !DateTime.TryParse((string)ControlValue, Thread.CurrentThread.CurrentCulture.DateTimeFormat,
            DateTimeStyles.None, out TValue))
        {
          throw new WebBindingException("Invalid date input");
        }
        else
        {
          AssignedValue = TValue;
        }
      }
      else if (bindingSourceType.IsEnum)
      {
        AssignedValue = Enum.Parse(bindingSourceType, (string)ControlValue);
      }
      else
      {
        throw new Exception("Field Type not Handled by Data unbinding");
      }

      WebDataBindingHelpers.SetPropertyEx(bindingSourceObject, bindingSourceMember, AssignedValue);
    }

    /// <summary>
    /// Returns a the control bindingsource and binding source member
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
      if (string.IsNullOrEmpty(BindingSource))
      {
        return base.ToString();
      }

      return BindingSource + "." + BindingSourceMember;
    }

    #region Hide Properties for the Designer

    [Browsable(false)]
    public override string ID
    {
      get { return base.ID; }
      set { base.ID = value; }
    }

    [Browsable(false)]
    public override bool Visible
    {
      get { return base.Visible; }
      set { base.Visible = value; }
    }

    [Browsable(false)]
    public override bool EnableViewState
    {
      get { return base.EnableViewState; }
      set { base.EnableViewState = value; }
    }

    #endregion
  }
}